package mediaCoreLib.core
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.TimerEvent;
	import flash.media.ID3Info;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundLoaderContext;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	import flash.utils.Timer;
	
	import mediaCoreLib.events.FadeEvent;
	
	[Event(name="complete", type="flash.events.Event")]
	[Event(name="ioError", type="flash.events.IOErrorEvent")]
	[Event(name="soundComplete", type="flash.events.Event")]
	
	[ExcludeClass]
	public class PlaylistObject extends EventDispatcher
	{
		///////////////////////////////////////////////////////
		//	STATIC VARs
		///////////////////////////////////////////////////////
		
		/** 
		 * Delay amount for the timer, in milliseconds.  Inversely affects length of fades.
		 * The higher the INTERVAL, the greater resolution of the fade. 
		 * */
		static private const INTERVAL:int = 100;
		
		// TODO: this should prob not be static, but instead be the sum of the fadeInLength + fadeOutLength.
		// 		 That way if sound.length is less than the sum of the fades, we should modify the fade lengths
		//	 	 so that they will be able to fade correctly. (future enhancment) MO 071008
		/**
		 * Minimum amount of time that a sound will play.
		 * */
		static public var MIN_PLAY_TIME:int = 1500;
		
		///////////////////////////////////////////////////////
		//	CONSOLE
		///////////////////////////////////////////////////////
		
		// need to have a way to check for useXFade and useFadeIn/useFadeOut
		public function play (doFade:Boolean = true):void
		{
			trace("play");
			if (!isLoaded && !_sound.hasEventListener(Event.COMPLETE))
				_sound.addEventListener(Event.COMPLETE, sound_completeHandler);
			
			else if (isLoaded && !isPlaying)
			{
				if (_channel && _channel.hasEventListener(Event.SOUND_COMPLETE))
					_channel.removeEventListener(Event.SOUND_COMPLETE, channel_soundCompleteHandler);
				
				_channel = _sound.play(currentPosition);
				
				// TODO - may need to move this before playing sound if we start to get pops,
				// but have it afterwards right now to work around null ref error
				if (doFade) 
					startFadeIn();
			 
				_channel.addEventListener(Event.SOUND_COMPLETE, channel_soundCompleteHandler);
				
				_isPlaying = true;
				
				dispatchEvent(new Event("isPlayingChanged")); //TODO - are we using this?  
				dispatchEvent(new Event(Event.OPEN));
			}
			
			else
				_isPlaying = true;
		}
		
		public function pause ():void
		{
			_currentPosition = _channel.position;
			_channel.stop();
			timer.stop()
			
			_isPlaying = false;
		}
		
		public function stop ():void
		{
			trace ("stop");
			_currentPosition = 0;
			_channel.stop();
			timer.stop();
			
			_isPlaying = false;
		}
		
		///////////////////////////////////////////////////////
		//  FADE IN VOLUME
		///////////////////////////////////////////////////////
		
		private var _fadeInVolume:Number = 0;
		
		public function get fadeInVolume ():Number
		{
			return _fadeInVolume;
		}
		
		public function set fadeInVolume (value:Number):void
		{
			_fadeInVolume = value;
		}
		
		///////////////////////////////////////////////////////
		//  FADE OUT VOLUME
		///////////////////////////////////////////////////////
		
		private var _fadeOutVolume:Number = 1;
		
		public function get fadeOutVolume ():Number
		{
			return _fadeOutVolume;
		}
		
		public function set fadeOutVolume (value:Number):void
		{
			_fadeOutVolume = value;
		}
		
		///////////////////////////////////////////////////////
		//  FADE IN LENGTH
		///////////////////////////////////////////////////////
		
		private var _fadeInLength:Number;
		private var _incrementIn:Number = .08;
		
		
		public function get fadeInLength ():Number
		{
			return _fadeInLength;
		}
		
		public function set fadeInLength (value:Number):void
		{
			_fadeInLength = value;
			_incrementIn = INTERVAL / _fadeInLength;
		}
		
		///////////////////////////////////////////////////////
		//  FADE OUT LENGTH
		///////////////////////////////////////////////////////
		
		private var _fadeOutLength:Number;
		private var _incrementOut:Number = .08;
		
		public function get fadeOutLength ():Number
		{
			return _fadeOutLength;
		}
		
		public function set fadeOutLength (value:Number):void
		{
			_fadeOutLength = value;
			_incrementOut = INTERVAL / _fadeOutLength;
		}
		
		///////////////////////////////////////////////////////
		//  FADE IN FUNCTIONS
		///////////////////////////////////////////////////////
		
		private function startFadeIn ():void
		{
			_fadeInVolume = 0;
			_isFadingIn = true;
			dispatchEvent(new Event("isFadingInChanged"));
			
			// TODO: instead of using fadeInVolume, maybe calculate based on channel.soundTransform.volume - increment
			changeVolume(_fadeInVolume, _channel);
			 
			// SET TIMER EVENT LISTENER
			if (!timer.running)
			{
				if (!timer.hasEventListener(TimerEvent.TIMER))
					timer.addEventListener(TimerEvent.TIMER, timer_timerHandler);
				
				timer.start(); // kicks off the fadeIn
				// TODO: figure out when best to stop timer
			}
		}
		
		private function fadeIn ():void
		{	
			// TODO - we need to figure out a way to use relative volumes in terms of a player volume setting rather than absolutes within the objects
			if (_fadeInVolume == 0 && _channel)
			{
				_fadeInVolume = _incrementIn;
				changeVolume(_fadeInVolume, _channel);	
				dispatchEvent(new Event(Event.OPEN));
			}
				
			else if (0 <= _fadeInVolume && _fadeInVolume < 1 )
			{
				_fadeInVolume += _incrementIn;
				changeVolume(_fadeInVolume, _channel);
			}
			
			else if (_fadeInVolume >= 1) 
			{
				_fadeInVolume = 1;
				_fadeOutVolume = 1;
				_isFadingIn = false;
				
				var evt:FadeEvent = new FadeEvent(FadeEvent.FADE_IN_COMPLETE);
				evt.position = _channel?
								_channel.position:
								currentPosition;
				
				dispatchEvent(evt);
			}
		}
		
		///////////////////////////////////////////////////////
		//  FADE OUT FUNCTIONS
		///////////////////////////////////////////////////////
		
		// function maybe be redundant here
		public function startFadeOut (suppressFadeOutEvent:Boolean = false):void
		{
			_isFadingOut = true;
			_suppressFadeOutEvent = suppressFadeOutEvent;
			
			// in case the timer isn't running (e.g. if we didn't fadeIn)
			// SET TIMER EVENT LISTENER
			if (!timer.running)
			{
				if (timer.hasEventListener(TimerEvent.TIMER))
					timer.addEventListener(TimerEvent.TIMER, timer_timerHandler);
				
				timer.start();  // kicks off the fade
			}	
		}
		
		/* 	fadeOut could get called in 2 cases:
		
			1. The currentPosition == (trackLength - incrementOut)
				- if NOT the last track in the playlist, then start next song for XFade
				- else, stop everything
				
			2. stop() is called explicitly in the middle of the track
				- TODO: might not fade out in this case, or do a small fade
		*/
		private var _suppressFadeOutEvent:Boolean = false;
		
		private function fadeOut ():void
		{
			// TODO: change to _fadeOutVolume == _currentVolume
			if (_fadeOutVolume == 1 && _channel)
			{
				_fadeOutVolume -= _incrementOut;
				// turn volume down to prevent clipping
				changeVolume(_fadeOutVolume, _channel);	
				// signify that the fade out has started.
				
				if (!_suppressFadeOutEvent) //because this triggers index++ in the mgr we should suppress this.  Is there a better way?
				{
					var evt:FadeEvent = new FadeEvent(FadeEvent.FADE_OUT_START);
					evt.position = _channel?
									_channel.position:
									currentPosition;
					
					dispatchEvent(evt);
				}
				
				_suppressFadeOutEvent = false; //reset it.  WTF who knows if this wiil work
			}
				
			else if (0 < _fadeOutVolume && _fadeOutVolume < 1 )
			{
				_fadeOutVolume -= _incrementOut;
				changeVolume(_fadeOutVolume, _channel);
			}
			
			else if (_fadeOutVolume <= 0) 
			{
				/* we're now done fading and the volume is at 0
				 this might not be the end of the song yet, so
				 the SoundComplete event hasn't been fired.
				 We also need to know if this is the last song in the playlist
				 so that we can stop the timer. */
				_isFadingOut = false;
				
				stop();
				timer.stop(); 
				
				dispatchEvent(new Event(Event.SOUND_COMPLETE));
			}
		}
	  	
	  	// TODO: get playlistmgr to hook into this for volume control
	  	// TODO: we may need to have a volume limit defined by the playlistMGR that basically say 
	  	// the max this guy can goto, which will override any other values set - justin
	  	public function changeVolume(value:Number, curChannel:SoundChannel = null):void
	  	{
	  		//TODO: Only allow volume to go up to 1 ...oh, this is similiar to justin's comment above
	  		trace("new volume = " + value);
	  		// if no channel is specified, use the default channel
	  		if (!curChannel)
	  			curChannel = _channel;
	  		
			curChannel.soundTransform = new SoundTransform(value);
	  	}
	  	
		///////////////////////////////////////////////////////
		// 	TIMER HANDLER
		///////////////////////////////////////////////////////

		private var timer:Timer = new Timer(INTERVAL);

		/**
		 * Amount of time before starting fade out
		 */ 
		// TODO: set this dynamically when the fadeLengths are changed
		private var fadeOutStartPoint:Number = 8000;
		
  		private function timer_timerHandler (event:TimerEvent):void
		{		
			// fadeOutStartPoint = how long until fade out starts
			// checkf or previews that are shorter than specified play length			
			// MIN_PLAY_TIME = default minimum play time
			if (fadeOutStartPoint > _sound.length && _sound.length > MIN_PLAY_TIME)
			{
				// the length is not correctly calculated until the song is fully loaded
				// which causes the player to think the song has already reached the fadeOutStartPoint time
				// and the player goes to the next song but also keeps playing the first one
				if (_sound.bytesLoaded < _sound.bytesTotal)
				{
					// song is not fully loaded so ignore
					
				}
				
				else
				{
					// song is fully loaded and is shorter than the typical sample length
					fadeOutStartPoint = _sound.length - fadeOutLength;
				}
			} 
			
			/* FADE IN */
			if (_isFadingIn) 
				fadeIn();
			
			trace("position = " +_channel.position);
			// Check to see if we've reached the fadeOutStartPoint	
			//if (_channel.position >= fadeOutStartPoint && !_isFadingIn)
			if (_channel.position >= (_sound.length - fadeOutLength) && !_isFadingIn)
				_isFadingOut = true;
			
			/* FADE OUT */
			if (_isFadingOut) 
				fadeOut();
		}
		
		///////////////////////////////////////////////////////
		//	LOAD
		///////////////////////////////////////////////////////
		
		public var autoLoad:Boolean = false;
		
		public function load (source:String = null):void
		{
			if (source)
				url = source;
				
			if (isLoaded)
				return;
				
			if (!url || url == "")
				throw new Error("parameter url is undefined");
			
			//if old sound, clear its listeners that may have not completed
			if (_sound)
			{
				_sound.removeEventListener(Event.COMPLETE, sound_completeHandler);
				_sound.removeEventListener(IOErrorEvent.IO_ERROR, sound_IOErrorHandler);
			}
			
			var request:URLRequest = new URLRequest(url);
			
			var context:SoundLoaderContext = new SoundLoaderContext();
			context.checkPolicyFile = true;
			
			_sound = new Sound();
			
			_sound.addEventListener(Event.COMPLETE, sound_completeHandler);
			_sound.addEventListener(IOErrorEvent.IO_ERROR, sound_IOErrorHandler);
			_sound.load(request, context);
		}
		
		///////////////////////////////////////////////////////
		//	URL
		///////////////////////////////////////////////////////
		
		private var _url:String;
		
		public function get url ():String
		{
			return _url;
		}
		
		public function set url (value:String):void
		{
			if (_url != value)
			{
				_url = value;
				_isLoaded = false;
				
				if (autoLoad)
					load();
			}
		}
		
		///////////////////////////////////////////////////////
		//	SONG LENGTH
		///////////////////////////////////////////////////////
	
		private var _songLength:Number;
		
		public function get songLength ():Boolean
		{
			return songLength;
		}
		
		///////////////////////////////////////////////////////
		//	IS PLAYING
		///////////////////////////////////////////////////////
		
		private var _isPlaying:Boolean = false;
		
		public function get isPlaying ():Boolean
		{
			return _isPlaying;
		}
		
		///////////////////////////////////////////////////////
		//	IS FADING IN
		///////////////////////////////////////////////////////
		
		private var _isFadingIn:Boolean = true;
		
		// TODO: Setup set function to notify plmgr of the value
		public function get isFadingIn ():Boolean
		{
			return _isFadingIn;
		}
		
		///////////////////////////////////////////////////////
		//	IS FADING OUT
		///////////////////////////////////////////////////////
		
		private var _isFadingOut:Boolean = false;
		
		[Bindable("isFadingOutChanged")]
		public function get isFadingOut ():Boolean
		{
			return _isFadingOut;
		}
		
		///////////////////////////////////////////////////////
		//	CURRENT POSITION
		///////////////////////////////////////////////////////
		
		private var _currentPosition:int;
		
		public function get currentPosition ():int
		{
			return _currentPosition;
		}
		public function set currentPosition (value:int) :void
		{
			_currentPosition = value;
		}
		
		///////////////////////////////////////////////////////
		//	IS LOADED
		///////////////////////////////////////////////////////
		
		private var _isLoaded:Boolean;
		
		public function get isLoaded ():Boolean
		{
			return _isLoaded;
		}
		
		///////////////////////////////////////////////////////
		//	SOUND
		///////////////////////////////////////////////////////
		
		private var _sound:Sound;
		private var _channel:SoundChannel = new SoundChannel;
		
		public function get sound ():Sound
		{
			return _sound;
		}
		
		
		///////////////////////////////////////////////////////
		//	ID3 INFO - TODO: PUT INTO MP3Object
		///////////////////////////////////////////////////////
		[Bindable] 
		public var id3Info:ID3Info;
		
		///////////////////////////////////////////////////////
		//	EVT HANDLERS
		///////////////////////////////////////////////////////
		
		/**
		 * @private
		 * 
		 * Handles the  event for when the media has played thru its entire length.
		 */
		private function channel_soundCompleteHandler (evt:Event = null):void
		{
			if (evt)
				evt.target.removeEventListener(Event.SOUND_COMPLETE, channel_soundCompleteHandler);
			
			else
				evt = new Event(Event.SOUND_COMPLETE);
			
			_isPlaying = false;
			dispatchEvent(evt);
		}
		
		/**
		 * @private
		 * 
		 * Handles the event dispatched when the sound has successfully loaded.
		 */
		private function sound_completeHandler (evt:Event):void
		{
			_isLoaded = true;
			
			_sound.removeEventListener(IOErrorEvent.IO_ERROR, sound_IOErrorHandler);
			_sound.removeEventListener(Event.COMPLETE, sound_completeHandler);
			
			// set ID3 info
			// TODO: set this info in PlaylistManager as currently playling info
			try
			{
				//artist = _sound.id3.artist;
				//songName = _sound.id3.songName;
				//album = _sound.id3.album;
				id3Info = _sound.id3;
			}
			
			catch (e:Error)
			{
				//wtf?
			}
			
			dispatchEvent(new Event(Event.COMPLETE));
		}
		
		/**
		 * @private
		 * 
		 * Handles the event for when the sound has encountered an error during the loading process.
		 */
		private function sound_IOErrorHandler (evt:IOErrorEvent):void
		{
			_isLoaded = false;
			
			_sound.removeEventListener(IOErrorEvent.IO_ERROR, sound_IOErrorHandler);
			_sound.removeEventListener(Event.COMPLETE, sound_completeHandler);
			
			dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR));
		}
	}
}